package com.mb.microservice.framework.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mb.microservice.framework.core.entity.SearchBaseEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @PackageName: com.mb.fabric.hongda.finance.utils
 * @ClassName: HttpRequestUtils
 * @Description: TODO
 * @author: ABing
 * @date: 2020/9/14
 */
@Slf4j
public class HttpRequestUtils {

    /** 全局连接池对象 */
    private static final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();

    /**
     * 静态代码块配置连接池信息
     */
    static {
        // 设置最大连接数
        connManager.setMaxTotal(30000);
        // 设置每个连接的路由数
        connManager.setDefaultMaxPerRoute(20);
    }

    /**
     * 获取Http客户端连接对象
     *
     * @return Http客户端连接对象
     */
    public static CloseableHttpClient getHttpClient() {
        // 创建Http请求配置参数
        RequestConfig requestConfig = RequestConfig.custom()
                // 获取连接超时时间
                .setConnectionRequestTimeout(Constants.CONNECTION_TIMEOUT)
                // 请求超时时间
                .setConnectTimeout(Constants.CONNECTION_TIMEOUT)
                // 响应超时时间
                .setSocketTimeout(Constants.CONNECTION_TIMEOUT)
                .build();

        /**
         * 测出超时重试机制为了防止超时不生效而设置
         *  如果直接放回false,不重试
         *  这里会根据情况进行判断是否重试
         */
        HttpRequestRetryHandler retry = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= 3) {// 如果已经重试了3次，就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                    return false;
                }
                if (exception instanceof InterruptedIOException) {// 超时
                    return true;
                }
                if (exception instanceof UnknownHostException) {// 目标服务器不可达
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                    return false;
                }
                if (exception instanceof SSLException) {// ssl握手异常
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };

        // 创建httpClient
        return HttpClients.custom()
                // 把请求相关的超时信息设置到连接客户端
                .setDefaultRequestConfig(requestConfig)
                // 把请求重试设置到连接客户端
                .setRetryHandler(retry)
                // 配置连接池管理对象
                .setConnectionManager(connManager)
                .build();
    }

    /**
     * GET请求
     *
     * @param url 请求地址
     * @return
     */
    public static String httpGet(String url) {
        String msg = "-1";

        // 获取客户端连接对象
        CloseableHttpClient httpClient = getHttpClient();
        // 创建GET请求对象
        HttpGet httpGet = new HttpGet(url);

        CloseableHttpResponse response = null;

        try {
            // 执行请求
            response = httpClient.execute(httpGet);
            // 获取响应实体
            HttpEntity entity = response.getEntity();
            // 获取响应信息
            msg = EntityUtils.toString(entity, "UTF-8");
        } catch (ClientProtocolException e) {
            log.error("协议错误", e);
        } catch (ParseException e) {
            log.error("解析错误", e);
        } catch (IOException e) {
            log.error("IO错误", e);
        } finally {
            if (null != response) {
                try {
                    EntityUtils.consume(response.getEntity());
                    response.close();
                } catch (IOException e) {
                    System.err.println("释放链接错误");
                    log.error("释放链接错误", e);
                }
            }
        }

        return msg;
    }

    public static <T> List<T> httpPostFromCouchDB(String url, String[] fields, Class c, SearchBaseEntity baseEntity){
        return httpPostFromCouchDB(url, fields, null, null, null, c, baseEntity);
    }


    public static <T> List<T> httpPostFromCouchDB(String url, String[] fields, JSONArray sorts, Integer limit, Integer skip, Class c, SearchBaseEntity baseEntity){
        List<JSONObject> ands = getAnds(c, baseEntity);
        if(ands == null || ands.isEmpty()){
            return null;
        }else{
            return httpPostFromCouchDB(url, ands, sorts, limit, skip, fields, c);
        }
    }

    public static <T> List<T> httpPostFromCouchDB(String url, List<JSONObject> selectors, String[] fields, Class c){
        return httpPostFromCouchDB(url, selectors, null, null, null, fields, c);
    }

    public static <T> List<T> httpPostFromCouchDB(String url, List<JSONObject> selectors, JSONArray sorts, Integer limit, Integer skip, String[] fields, Class c){
        if(selectors == null || selectors.size() == 0){
            return null;
        }
        JSONArray and = new JSONArray();
        selectors.forEach(selector ->{
            and.add(selector);
        });
        JSONObject params = new JSONObject();
        params.put("$and", and);
        JSONObject query = new JSONObject();
        query.put("selector", params);
        if(fields != null && fields.length > 0){
            query.put("fields", fields);
        }
        if(sorts != null && !sorts.isEmpty()){
            query.put("sort", sorts);
        }
        if(limit != null && limit>0){
            query.put("limit", limit);
            return httpPostFromCouchDB(url, query, c);
        }
        if(skip != null && skip>0){
            query.put("skip", skip);
        }
        query.put("limit", Constants.LIMIT);
        int page = 0;
        List<T> list = new ArrayList();
        for(;;) {
            query.put("skip", page * Constants.LIMIT);
            List<T> ts = httpPostFromCouchDB(url, query, c);
            if(ts == null || ts.size() == 0){
                break;
            }
            list.addAll(ts);
            page++;
        }
        if(list.size() == 0){
            return null;
        }
        return list;
    }

    public static <T> List<T> httpPostFromCouchDB(String url, JSONObject query, Class c){
        if(query.isEmpty()){
            return null;
        }
        List<T> list = new ArrayList();
        // 创建默认的httpClient实例
//        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpClient httpClient =  getHttpClient();
        HttpPost httpPost = new HttpPost(url);
        // httpPost设置JSON格式的参数
        httpPost.setHeader("Content-Type", "application/json");
        httpPost.setHeader("accept", "application/json");
        httpPost.setHeader("Accept-Encoding", "gzip, deflate");
        httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");
        StringEntity requestEntity = new StringEntity(query.toString(),"UTF-8");
        requestEntity.setContentType("application/json");
        httpPost.setEntity(requestEntity);
        CloseableHttpResponse response = null;
        try{
            // 发送HttpPost请求，获取返回值
            response =  httpClient.execute(httpPost);
            if(response != null) {
                HttpEntity resEntity = response.getEntity();
                if (resEntity != null) {
                    String returnValue = EntityUtils.toString(resEntity, "utf-8");
                    JSONObject root = JSONObject.parseObject(returnValue);
                    JSONArray jsonArray = root.getJSONArray("docs");
                    Iterator iterator = jsonArray.iterator();
                    while(iterator.hasNext()) {
                        JSONObject jsonObject = (JSONObject)iterator.next();
                        Object t = JsonUtils.json2Bean(jsonObject.toString(), c);
                        list.add((T)t);
                    }
                    return list;
                }
            }

        }catch (ClientProtocolException e) {
            log.error("协议错误", e);
        } catch (ParseException e) {
            log.error("解析错误", e);
        } catch (IOException e) {
            log.error("IO错误", e);
        } finally {
            if (null != response) {
                try {
                    EntityUtils.consume(response.getEntity());
                    response.close();
                } catch (IOException e) {
                    System.err.println("释放链接错误");
                    log.error("释放链接错误", e);
                }
            }
        }

        return null;
    }

    public static List<JSONObject> getAnds(Class c, SearchBaseEntity baseEntity){
        if(baseEntity == null){
            return null;
        }
        List<JSONObject> ands = baseEntity.getAnds();
        if(ands == null){
            ands = new ArrayList<>();
        }
        Field[] fields = c.getDeclaredFields();
        for(Field field : fields){
            try {
                field.setAccessible(true);
                Object value = field.get(baseEntity);
                String fieldName = field.getName();
                if(value != null && StringUtils.isNotBlank(value.toString()) && (!isFind(fieldName, ands)) ){
                    JSONObject expression = new JSONObject();
                    expression.put("$eq", value.toString());
                    JSONObject param = new JSONObject();
                    param.put(fieldName, expression);
                    ands.add(param);
                }
            } catch (IllegalAccessException e) {
                log.error(e.getMessage(), e);
            }
        }
        if(ands.size() > 0){
            return ands;
        }
        return null;
    }

    private static boolean isFind(String fieldName, List<JSONObject> ands){
        if(ands == null || ands.isEmpty()){
            return false;
        }
        int i = 0;
        for(; i < ands.size(); i++){
            if(ands.get(i).containsKey(fieldName)){
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {

        System.out.println(httpGet("http://www.baidu.com"));

    }
}
